package com.rizzo.analytics.services.web.delegator;

import com.google.common.collect.Lists;
import com.rizzo.analytics.domain.account.Account;
import com.rizzo.analytics.domain.trigger.Trigger;
import com.rizzo.analytics.services.repo.AccountRepository;
import com.rizzo.analytics.services.repo.TriggerRepository;
import com.rizzo.analytics.services.web.controller.v1.error.AccountNotFoundException;
import com.rizzo.analytics.services.web.controller.v1.error.DuplicateEntityException;
import com.rizzo.analytics.services.web.controller.v1.error.MalformedRequestException;
import com.rizzo.analytics.services.web.controller.v1.error.TriggerNotFoundException;
import org.hibernate.exception.ConstraintViolationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

public class AccountDelegatorImpl extends RizzoDelegator implements AccountDelegator {

	private static final Logger LOGGER = LoggerFactory.getLogger(AccountDelegatorImpl.class);

	@Autowired
	private AccountRepository accountRepository;

	@Autowired
	private TriggerRepository triggerRepository;

	@Override
	public List<Account> getAccounts(String user) {
		LOGGER.debug("User [account : '{}'] is asking for all the accounts", user);
		List<Account> accounts = Lists.newArrayList();
		for (Account account : accountRepository.findAll()) {
			accounts.add(account);
		}
		return accounts;
	}

	@Override
	@Transactional
	public Account saveAccount(String user, Account account) throws MalformedRequestException, DuplicateEntityException {
		if (account.getId() == null) {
			account.prepareBeforeSave();
			final Account savedAccount;
			try {
				savedAccount = accountRepository.save(account);
			} catch (DataIntegrityViolationException e) {
				throw new DuplicateEntityException(40901, "service.account.save.duplicate");
			} catch (ConstraintViolationException e) {
				throw new DuplicateEntityException(40901, "service.account.save.duplicate");
			}
			LOGGER.debug("User [account : '{}'] is saving the account with [username : '{}']", user, account.getUsername());
			return savedAccount;
		} else {
			throw new MalformedRequestException(40001, "service.account.save.malformed");
		}
	}

	@Override
	@Transactional
	public Account updateAccount(String user, Account account) throws AccountNotFoundException, MalformedRequestException {
		if (account.getId() != null) {
			Account persistentAccount = accountRepository.findOne(account.getId());
			if (persistentAccount != null) {
				LOGGER.debug("User with [username : '{}'] is updating the [account : '{}']", user, account.getId());
				persistentAccount.mergeBeforeUpdate(account);
				return accountRepository.save(persistentAccount);
			} else {
				// account to be updated doesn't exist
				throw new AccountNotFoundException(40401, "service.account.update.not.found", new Object[]{account.getId().toString()});
			}
		} else {
			// updating an account without providing
			throw new MalformedRequestException(40002, "service.account.update.malformed");
		}
	}

	@Override
	@Transactional
	public void deleteAccount(String user, Long accountId) throws AccountNotFoundException {
		if (accountRepository.exists(accountId)) {
			LOGGER.debug("User with [username : '{}'] is deleting the account with [id : '{}']", user, accountId);
			accountRepository.delete(accountId);
		} else {
			// account to be deleted doesn't exist
			throw new AccountNotFoundException(40401, "service.account.delete.not.found", new Object[]{accountId.toString()});
		}
	}

	@Override
	@Transactional
	public Account getAccount(String user, Long accountId) throws AccountNotFoundException {
		LOGGER.debug("Asking for account [id : '{}' ] with [user : '{}']", accountId, user);
		Account account = accountRepository.findOne(accountId);
		if (account != null && hasRoleAdmin()) {
			return account;
		} else if (account != null && account.getUsername().equals(user)) {
			return account;
		} else {
			// account doesn't exist or the user tries to access an account that is not his to view
			throw new AccountNotFoundException(40401, "service.account.get.not.found", new Object[]{accountId.toString()});
		}
	}

	@Override
	@Transactional
	public List<Trigger> getTriggers(String user, Long accountId) throws AccountNotFoundException {
		final Account account = accountRepository.findOne(accountId);
		if (account != null) {
			if (hasRoleAdmin()) {
				LOGGER.debug("Asking for all the triggers of [account : '{}'] with [user : '{}']", accountId, user);
				return account.getTriggers();
			} else if (account.getUsername().equals(user)) {
				LOGGER.debug("Asking for all the triggers of [account : '{}'] with [user : '{}']", accountId, user);
				return account.getTriggers();
			} else {
				// user tries to access triggers that aren't his to view
				throw new AccountNotFoundException(40401, "service.account.get.not.found", new Object[]{accountId.toString()});
			}
		} else {
			// account doesn't exist
			throw new AccountNotFoundException(40401, "service.account.get.not.found", new Object[]{accountId.toString()});
		}
	}

	@Override
	@Transactional
	public Trigger getTrigger(String user, Long accountId, Long triggerId) throws AccountNotFoundException, TriggerNotFoundException {
		if (accountRepository.exists(accountId)) {
			final Trigger trigger = triggerRepository.findOne(triggerId);
			if (trigger != null) {
				if (trigger.getAccount() != null && trigger.getAccount().getId().equals(accountId)) {
					if (hasRoleAdmin()) {
						LOGGER.debug("Asking for the trigger with [triggerId : '{}'] of [account : '{}'] with [user : '{}']",
								triggerId, accountId, user);
						return trigger;
					} else if (trigger.getAccount().getUsername().equals(user)) {
						LOGGER.debug("Asking for the trigger with [triggerId : '{}'] of [account : '{}'] with [user : '{}']",
								triggerId, accountId, user);
						return trigger;
					} else {
						// user tries to access a trigger that is not his
						throw new TriggerNotFoundException(40402, "service.account.get.trigger.not.found",
								new Object[]{triggerId.toString(), accountId.toString()});
					}
				} else {
					// user tries to access a trigger that is not his (account-id and trigger-id don't match)
					throw new TriggerNotFoundException(40402, "service.account.get.trigger.not.found",
							new Object[]{triggerId.toString(), accountId.toString()});
				}
			} else {
				// trigger does not exist in the system
				throw new TriggerNotFoundException(40402, "service.account.get.trigger.not.found",
						new Object[]{triggerId.toString(), accountId.toString()});
			}
		} else {
			// requested account does not exist
			throw new AccountNotFoundException(40401, "service.account.get.not.found",
					new Object[]{accountId.toString()});
		}
	}

	@Override
	@Transactional
	public Trigger saveTrigger(String user, Long accountId, Trigger trigger) throws AccountNotFoundException, TriggerNotFoundException {
		final Account account = accountRepository.findOne(accountId);
		if (account != null) {
			if (trigger.getId() == null) {
				trigger.prepareBeforeSave();
				if (hasRoleAdmin()) {
					// if the user is admin he can do the insert/link
					trigger.setAccount(account);
					account.getTriggers().add(trigger);
					triggerRepository.save(trigger);
					return trigger;
				} else if (account.getUsername().equals(user)) {
					// only if the account belongs to the user can he insert a trigger
					trigger.setAccount(account);
					account.getTriggers().add(trigger);
					triggerRepository.save(trigger);
					return trigger;
				} else {
					// user tries to insert a trigger for another account
					throw new AccountNotFoundException(40401, "service.trigger.save.account.not.found",
							new Object[]{accountId.toString()});
				}
			} else {
				throw new MalformedRequestException(40003, "service.trigger.save.malformed");
			}
		} else {
			// referred account does not exist
			throw new AccountNotFoundException(40401, "service.trigger.save.account.not.found",
					new Object[]{accountId.toString()});
		}
	}

	@Override
	@Transactional
	public Trigger updateTrigger(String user, Long accountId, Trigger trigger) throws AccountNotFoundException {
		final Account account = accountRepository.findOne(accountId);
		if (account != null) {
			if (trigger.getId() != null) {
				Trigger persistentTrigger = account.getTrigger(trigger.getId());
				if (persistentTrigger != null) {
					if (hasRoleAdmin()) {
						// if the user is admin he can do the update
						persistentTrigger.mergeBeforeUpdate(trigger);
						return triggerRepository.save(persistentTrigger);
					} else if (account.getUsername().equals(user)) {
						// only if the account belongs to the user can he update a trigger
						persistentTrigger.mergeBeforeUpdate(trigger);
						return triggerRepository.save(persistentTrigger);
					} else {
						// user tries to update a trigger for another account
						throw new AccountNotFoundException(40401, "service.trigger.update.account.not.found",
								new Object[]{accountId.toString()});
					}
				} else {
					// the provided trigger to update doesn't exist
					throw new TriggerNotFoundException(40403, "service.account.update.trigger.not.found",
							new Object[]{trigger.getId().toString(), accountId.toString()});
				}
			} else {
				throw new MalformedRequestException(40004, "service.trigger.update.malformed");
			}
		} else {
			// referred account does not exist
			throw new AccountNotFoundException(40401, "service.trigger.update.account.not.found",
					new Object[]{accountId.toString()});
		}
	}

	@Override
	@Transactional
	public void deleteTrigger(String user, Long accountId, Long triggerId) throws AccountNotFoundException, TriggerNotFoundException {
		Account account = accountRepository.findOne(accountId);
		if (account != null) {
			if (triggerRepository.exists(triggerId)) {
				if (hasRoleAdmin()) {
					LOGGER.debug("Deleting trigger with [triggerId : '{}'] of [account : '{}'] with [user : '{}']",
							triggerId, accountId, user);
					boolean deleted = account.removeTrigger(triggerId);
					if (deleted) {
						accountRepository.save(account);
					} else {
						// user tries to delete a trigger that is not his (account-id and trigger-id don't match)
						throw new TriggerNotFoundException(40402, "service.account.get.trigger.not.found",
								new Object[]{triggerId.toString(), accountId.toString()});
					}
				} else if (account.getUsername().equals(user)) {
					LOGGER.debug("Deleting trigger with [triggerId : '{}'] of [account : '{}'] with [user : '{}']",
							triggerId, accountId, user);
					boolean deleted = account.removeTrigger(triggerId);
					if (deleted) {
						accountRepository.save(account);
					} else {
						// user tries to delete a trigger that is not his (account-id and trigger-id don't match)
						throw new TriggerNotFoundException(40402, "service.account.get.trigger.not.found",
								new Object[]{triggerId.toString(), accountId.toString()});
					}
				} else {
					// user tries to delete a trigger that is not his
					throw new TriggerNotFoundException(40402, "service.account.get.trigger.not.found",
							new Object[]{triggerId.toString(), accountId.toString()});
				}

			} else {
				// trigger does not exist in the system
				throw new TriggerNotFoundException(40402, "service.account.get.trigger.not.found",
						new Object[]{triggerId.toString(), accountId.toString()});
			}
		} else {
			// requested account does not exist
			throw new AccountNotFoundException(40401, "service.account.get.not.found",
					new Object[]{accountId.toString()});
		}
	}
}
